/**
*  Copyright (c) 2011, Alex Theodoridis
*  All rights reserved.

*  Redistribution and use in source and binary forms, with
*  or without modification, are permitted provided that the
*  following conditions are met:
*  Redistributions of source code must retain the above
*  copyright notice, this list of conditions and the following disclaimer.
*  Redistributions in binary form must reproduce the above
*  copyright notice, this list of conditions and the following
*  disclaimer in the documentation and/or other materials
*  provided with the distribution.

*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS
*  AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
*  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
*  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
*  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
*  ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
*  OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
*  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
*  OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
*  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
*  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
*  ANY WAY OUT OF THE USE OF THIS SOFTWARE,
*  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
*/

#ifndef NeuronH
#define NeuronH

#include <stdlib.h>
#include <time.h>
#include <map>
#include <NeuralNetwork/Neuron/NeuronInput.h>
#include <NeuralNetwork/Neuron/Equation/IEquation.h>
#include <NeuralNetwork/NNException.h>
#include <Utilities/SmartPtr//SharedPtr.h>
#include <NeuralNetwork/Serialization/INeuronStream.h>
#include <NeuralNetwork/Neuron/Equation/IEquationFactory.h>
#include <NeuralNetwork/Serialization/NeuronMemento.h>
#include <NeuralNetwork/Neuron/INeuron.h>

namespace nn {
    /**
     * Neuron class.
     * Represent the neuron in the Neural layer.
     * Contains equation for output value calculation
     * and list of accepted inputs.
     */
    template<class Var>
    class Neuron : public INeuron<Var>{
    public:
        /**
         * Needed for static randomize call.
         */
        static const bool CONST_IS_RANDOMIZED;

    private:
        static bool startRandomization() throw();

    private:
        /**
         * Instance of output calculation equation.
         * The equation should be provided by implementation of IEquationFactory interface.
         */
        utils::SharedPtr<IEquation<Var> > _equation;

        /**
         * List of neurons inputs.
         */
        std::map<unsigned int, NeuronInput<Var> > _inputs;

        /**
         *	The neurons output.
         *	The output will be calculated with using the instance of
         *	calculation equation.
         */
        Var _output;

        /**
         * Neurons weight.
         * Needed in order to improve
         * the flexibility of neural network.
         */
        Var _neuronWeight;

        /**
         *	Needed to calculate the
         *	neurons output value.
         */
        Var _sum;

        /**
         *  The EquationFactory
         */
        typename utils::SharedPtr<IEquationFactory<Var> > _equationFactory;
        
        /**
        *   Lat object's memento taken by user.
        */
        mutable NeuronMemento<Var> _lastMemento;

    public:
        /**
         * Initialization constructor.
         * @param inputsNumber the number of inputs for current neuron.
         * @param equationFactory implementation of IEquationFactory interface
         * @exception NNException thrown on object initialization failure.
         */
        Neuron ( unsigned int inputsNumber, const typename utils::SharedPtr<IEquationFactory<Var> >& equationFactory ) {
            if ( equationFactory == NULL ) {
                throw NNException ( "Wrong argument equationFactory==NULL", __FILE__, __LINE__  );
            }

            if ( inputsNumber == 0 ) {
                throw NNException ( "Wrong argument inputsNumber==0", __FILE__, __LINE__ );
            }

            if ( !CONST_IS_RANDOMIZED ) {
                throw NNException ( "Randomization failed", __FILE__, __LINE__  );
            }

            _equationFactory = equationFactory;
            _equation = equationFactory->getInstance();
            if ( _equation == NULL ) {
                throw NNException ( "Failed with return NULL", __FILE__, __LINE__  );
            }

            int randNumb = rand();
            _neuronWeight = float ( float ( randNumb % 100 ) / 100.0f );
            _output = 0;
            _sum = 0;

            for ( unsigned int i = 0; i < inputsNumber; i++ ) {
                float weight = float ( float ( rand() % 100 ) / 100.0f );
                NeuronInput<Var> input ( 0, weight );
                if ( _inputs.insert ( std::make_pair ( i, input ) ).second == false ) {
                    throw NNException ( "Neuron::Neuron : Memory allocation error", __FILE__, __LINE__ );
                }
            }
        }

        /**
         * Initialization constructor.
         * @param inputsNumber the number of inputs for current neuron.
         * @param equation implementation of IEquation interface
         * @exception NNException thrown on object initialization failure.
         */
        Neuron ( unsigned int inputsNumber, const typename utils::SharedPtr<IEquation<Var> >& equation ) {
            if ( equation == NULL ) {
                throw NNException ( "Wrong argument equation==NULL", __FILE__, __LINE__ );
            }

            if ( inputsNumber == 0 ) {
                throw NNException ( "Wrong argument inputsNumber==0", __FILE__, __LINE__ );
            }

            if ( !CONST_IS_RANDOMIZED ) {
                throw NNException ( "Randomization failed", __FILE__, __LINE__  );
            }

            _equation = equation;
            int randNumb = rand();
            _neuronWeight = float ( float ( randNumb % 100 ) / 100.0f );
            _output = 0;
            _sum = 0;

            for ( unsigned int i = 0; i < inputsNumber; i++ ) {
                float weight = float ( float ( rand() % 100 ) / 100.0f );
                NeuronInput<Var> input ( 0, weight );
                if ( _inputs.insert ( std::make_pair ( i, input ) ).second == false ) {
                    throw NNException ( "Memory allocation error", __FILE__, __LINE__ );
                }
            }
        }

        /**
         *	Will set the inputs weight value.
         *	@param weightId the inputs identifier to set the new weight value.
         *	@param weigh a new weight value.
         */
        bool setInputWeight ( unsigned int weightId, const Var& weight ) {
            typename std::map<unsigned int, NeuronInput<Var> >::iterator curInput = _inputs.find ( weightId );
            if ( curInput == _inputs.end() ) {
                return false;
            }

            curInput->second.setInputWeight ( weight );
            return true;
        }

        const NeuronMemento<Var>& getMemento() const {
            _lastMemento.setInputs ( _inputs );
            _lastMemento.setOutput ( _output );
            _lastMemento.setNeuronWeight ( _neuronWeight );
            _lastMemento.setSum ( _sum );
            return _lastMemento;
        }

        bool setMemento ( const NeuronMemento<Var>& memento ) {
            bool result=false;
            if ( memento.getInputs().size() == _inputs.size() ) {
                _inputs=memento.getInputs();
                _output=memento.getOutput();
                _neuronWeight=memento.getNeuronWeight();
                _sum=memento.getSum();
                result=true;
            }

            return result;
        }

        /**
         *	Will set the input value.
         *	@param inputId the inputs identifier to set a new value.
         *	@param inputValue a new inputs value.
         */
        bool setInput ( unsigned int inputId, const Var& value ) {
            bool result = false;
            typename std::map<unsigned int, NeuronInput<Var> >::iterator curInput = _inputs.find ( inputId );
            if ( curInput != _inputs.end() ) {
                curInput->second.setValue ( value );
                result = true;
            }

            return result;
        }

        /**
         * Will set the value of neurons weight.
         * @param weight neurons weight value.
         */
        bool setNeuronWeight ( Var weight ) {
            _neuronWeight = weight;
            return true;
        }

        /**
         * Will return the neurons output after
         * equation calculations.
         */
        const Var& getOutput (  ) const {
            return _output;
        }

        /**
         * Will return the number of inputs for current neuron.
         */
        unsigned int getInputsNumber (  ) {
            return _inputs.size();
        }

        /**
         * Will return the input value for given input id
         * @param inputId input identifier
         * @exception NNexception throw in case of invalid inputId.
         */
        Var getInput ( unsigned int inputId ) {
            typename std::map<unsigned int, NeuronInput<Var> >::iterator curInput = _inputs.find ( inputId );
            if ( curInput == _inputs.end() ) {
                throw NNException ( "Wrong argument inputId", __FILE__, __LINE__ );
            }

            return curInput->second.getValue();
        }

        /**
         * Will return the weight of the input for given inputId
         * @param inputId input identifier.
         * @exception NNexception throw in case of invalid inputId.
         */
        Var getInputWeight ( unsigned int inputId ) {
            typename std::map<unsigned int, NeuronInput<Var> >::iterator curInput = _inputs.find ( inputId );
            if ( curInput == _inputs.end() ) {
                throw NNException ( "Wrong argument weightId", __FILE__, __LINE__ );
            }

            return curInput->second.getInputWeight();
        }

        /**
         * Will return the current value of neurons weight.
         */
        const Var& getNeuronWeight (  ) const {
            return _neuronWeight;
        }

        /**
         * Will calculate the neurons output value
         * for current neuron state.
         * @return true if succeed false otherwise.
         */
        bool calculateOutput (  ) {
            bool result = false;
            if ( _equation != NULL ) {
                _output = _equation->calculateEquation ( _neuronWeight, _inputs );
                result = true;
            }

            return result;
        }

        /**
         * Will return the calculation equation.
         */
        const utils::SharedPtr< IEquation<Var> >& getEquation (  ) {
            return _equation;
        }
        
        /**
         * Will set the given equation.
         * @param equation Instance of IEquation interface
         * implementation which represent the equation.
         */
        bool setEquation( const utils::SharedPtr< IEquation<Var> >& equation ){
            bool result = false;
            if( _equation != equation ){
                 _equation = equation;
                 result = true;
            }
           
            return result;
        }

        /**
         * Destructor.
         */
        virtual ~Neuron ( void ) throw () {}
    };

    template<class Var>
    const bool Neuron<Var>::CONST_IS_RANDOMIZED = Neuron<Var>::startRandomization();

    template<class Var>
    bool Neuron<Var>::startRandomization() throw() {
        time_t t = time ( NULL );
        srand ( t );
        return true;
    }

}

#endif
// kate: indent-mode cstyle; space-indent on; indent-width 4; 
